"""
Function checks the connection of a new mailbox
Written By: Daniel Ortiz
"""
from network.imap4 import IMAP4Factory
from network.pop3 import POP3Factory
from twisted.internet import reactor, ssl, defer
from twisted.python.failure import Failure
from twisted.mail import smtp
from database.mapclass import *
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class MailboxCheckHandler(object):
    """Funcion checks the validity of mailbox"""
    def __init__(self, parent):
        self.parent = parent
        
    def BindVisualization(self, vis):
        """Bind the current visualization to the handler"""
        self.addframe = vis
        vis.callback = self.CheckNewObject
        
    def BindDatabaseHandler(self, h):
        """Bind to visualization"""
        self.database_handler = h
        
    def CheckNewObject(self, obj, iframe):
        """Check the validity of the user created object. For example, for
        a mailbox domain pair, the server will be contacted, email will be
        sent to assure that the complete cycle is valid"""
        self.check_mailbox = obj
        self.CheckServer(self.check_mailbox)
        
    def CheckServer(self, mailbox):
        """Function checks the validity of the domain mailbox pair by querying the
        domain"""
        if mailbox.protocol == 'pop3':
            f = POP3Factory(str(mailbox.username), str(mailbox.password), self.CheckServerResults, 
                             mailbox, check = True)
        elif mailbox.protocol == 'imap4':
            f = IMAP4Factory(str(mailbox.username), str(mailbox.password), self.CheckServerResults, None,
                             mailbox, check = True)
        self.mail_state = 'SENT'
        if mailbox.issl:
            reactor.connectSSL(str(mailbox.iserver), mailbox.iport, f, ssl.ClientContextFactory(), timeout = 30)
        else:
            reactor.connectTCP(str(mailbox.iserver), mailbox.iport, f, timeout = 30)

        # Deals with reactor reentrant problem with an incorrect SSL context
        reactor.callLater(30, self.OnCheckConnectTimeout, f, mailbox)

    def OnCheckConnectTimeout(self, f, d):
        """Handle a connection timeout failure due to an incorrect connection
        mechanism"""
        if self.mail_state == 'SENT':
            if isinstance(f, IMAP4Factory):
                f.proto.logout()
            else:
                f.proto.quit()

            if d.issl:
                error = 'Connection timed out over SSL'
            else:
                error = 'Connection timed out without SSL'
            self.addframe.UpdateCheckStatus(False, 'Failure', error)
    

    def CheckServerResults(self, result, reason = ''):
        """Parse the 'Check' results of the server"""
        self.mail_state = 'RECV'
        if isinstance(reason, Failure):
            failure_string = reason.getErrorMessage()
            print reason
            if failure_string.find('DNS') != -1:
                error = failure_string.split('(')[0]
            else:
                error = 'Connection timeout due to unresolved port'
            self.addframe.UpdateCheckStatus(False, 'Failure', error)
        else:
            if result:
                self.addframe.UpdateCheckStatus(1, 'Incoming Server Contact Successful')
                self.CheckMail()
            else:
                self.addframe.UpdateCheckStatus(False, 'Failed to connect to server', reason)

    def CheckMail(self):
        """Check the outgoing mail"""
        message = MIMEMultipart()
        tfrom = 'KuplaCreator@kuplaproject.com'
        address = self.check_mailbox.username + '@'+ self.check_mailbox.domain
        message['From'] = tfrom
        message['To'] = address
        message['Subject'] = 'KuplaMail Test'

        source = """Hello,

        Welcome to KuplaMail! Get started with Help->Getting Started, or feel
        free to ask questions at the user forums (www.kuplaproject.com/KuplaDiscuss).
        You can also drop me a line at KuplaCreator@kuplaproject.com.

    Enjoy the Kupla Experience,
    Dan
        """
        text = MIMEText(source, _subtype = 'plain')
        message.attach(text)
        d = defer.Deferred().addBoth(self.CheckEmailOutput)
        messageData = message.as_string(unixfrom = False)
        f = smtp.ESMTPSenderFactory(self.check_mailbox.username,
                                    self.check_mailbox.password,
                                    tfrom, address, StringIO(str(messageData)),
                                    d)
        self.check_email_state = 'SENT' 
        reactor.callLater(30, self.OnCheckEmailTimeout)
        if self.check_mailbox.ossl:
            reactor.connectSSL(str(self.check_mailbox.oserver), self.check_mailbox.oport, f, ssl.ClientContextFactory())
        else:
            reactor.connectTCP(str(self.check_mailbox.oserver), self.check_mailbox.oport, f)


    def OnCheckEmailTimeout(self):
        """Check if the email conenction has timed out"""
        if self.check_email_state == 'SENT':
            error = 'Connection timeout due to unresolved port'
            self.addframe.UpdateCheckStatus(False, 'Failure', error)

            # For possible ackward timing errors and race conditions
            self.check_email_state = 'IGNORED'
            
    def CheckEmailOutput(self, result):
        """Parse the SMTP Reply code from the SMTP server"""
        if self.check_email_state == 'SENT':
            self.check_email_state = 'RECEIVED'
            if isinstance(result, Failure):
                failure_string = result.getErrorMessage()
                print failure_string
                if failure_string.find('Credentials') != -1:
                    error = 'Username password pair do not match'
                elif failure_string.find('SSL') == -1:
                    error = failure_string.split('(')[0]
                else:
                    error = 'SSL not supported for outgoing server'
                self.addframe.UpdateCheckStatus(False, 'Failure', error) 
            else:
                smtp_reply_code = result[1][0][1]
                reason = result[1][0][2]
                if smtp_reply_code == 250: # OK, email completed
                    self.addframe.UpdateCheckStatus(2, 'Outgoing Server Contact Succesful')
                    self.FinalizeCheck()
                else:
                    self.addframe.UpdateCheckStatus(False, 'Failed to contact outgoing server', reason)

    def FinalizeCheck(self):
        """Finalize the check of the object to the database"""
        self.database_handler.FinalizeCheck(self.addframe, self.check_mailbox)
